package main

func main() {
	/*
		可以通过 go run -gcflags "-m -l" ./文件名.go
		1.知道 golang 的内存逃逸吗？什么情况下会内存逃逸？
			golang	程序变量会携带有一组效验数据，用来证明 它的整个生命周期 是否在运行时 完全可知。
		  如果变量通过了这些校验，它就可以在 栈 上分配。否则，就说它 逃逸了，必须在 堆 上分配。

		2.逃逸分析：
			逃逸分析是编译器在静态编译的时候，分析对象的生命周期及引用情况来决定对象内存分配到堆上还是栈上，
		  由于“栈内存分配较堆快且栈内存回收较快（无需gc）”，编译器以此来优化程序性能。
			在函数中申请一个新的对象：
				如果分配在栈中，则函数执行结束后可 自动将内存回收
				如果分配在堆中，则函数执行结束后可 交给GC进行处理

		3.逃逸策略：
			如果函数外部 没有 引用，则优先放到 栈 中；
			如果函数外部 存在 引用，则优先放到 堆 中。

		注：注意：仅在函数内部使用的变量，也有可能放到堆中，比如内存过大超过栈的存储能力。

		4.逃逸场景：
			1）指针逃逸
			2）栈空间不足逃逸
			3）动态类型逃逸
			4）闭包引用对象逃逸

		5.总结：
			1)栈上分配内存比在堆上分配内存有更高的效率
			2）栈上分配内存不需要 GC 处理
			3）堆上分配内存使用完毕会交给 GC 处理
			4）逃逸分析的目的是 决定分配地址是 栈还是堆
			5）逃逸分析在 编译阶段 进行

		6.思考：两数传递指针 真的比 传递值 的效率高 吗？
			我们知道传递指针可以减少底层值的复制，可以提高效率，
			但是如果复制的数据量小，由于指针传递会产生逃逸，则可能会使用堆，也可能增加 GC 的负担，所以传递指针不一定是高效的。

		7.能引起变量逃逸到堆上的典型情况：
			1）函数返回局部变量的指针
				局部变量原本应该在栈中分配，在栈中回收。但是由于返回时被外部引用，因此其生命周期大于栈，则溢出。

			2）发送指针或带有指针的值到 channel 中
				在编译时，是没有办法知道哪个 goroutine 会在 channel 上接收数据。所以编译器没法知道变量什么时候才会被释放。

			3）在一个切片上存储指针或带指针的值
				 一个典型的例子就是 []*string 。这会导致切片的内容逃逸。尽管其后面的数组可能是在栈上分配的，但其引用的值一定是在堆上。、
				这是因为切片本身是一个引用类型，它包含一个指向底层数组的指针和长度信息。
				当切片中存储的元素是指针或带指针的值时，这些指针需要分配在堆上，以便它们可以在函数调用结束后仍然存在。

			4）slice 的背后数组被重新分配了，因为 append 时可能会超出其容量（cap）
				slice 初始化的地方在编译时是可以知道的，它最开始会在栈上分配。如果切片背后的存储要基于运行时的数据进行扩充，就会在堆上分配。

			5）在 interface 类型上调用方法
				在 interface 类型上调用方法都是动态调度的 —— 方法的真正实现只能在运行时知道。
			  想像一个 io.Reader 类型的变量 r , 调用 r.Read(b) 会使得 r 的值和切片b 的背后存储都逃逸掉，所以会在堆上分配。

			6）栈空间不足逃逸
				当对象大小超过栈帧的大小时，变量对象发生逃逸被分配到堆上

			7）当对象不确定大小或者被作为不确定大小的参数时发生逃逸。

			8）在给切片或者 map 赋值对象指针（也就是说将一个指向对象的指针存储在切片或 map 中），对象会逃逸到堆上。
			 但 赋值对象值 或者 返回对象值切片 是不会发生逃逸的。

				由于指针本身的大小较小，因此它可以直接存储在栈上。
			  	但对于对象本身，由于其较大的内存占用，需要分配在堆上，
			  	并且由于该对象可能被多个切片或 map 共享，因此必须在堆上分配，以避免数据竞争等问题。

				当 赋值对象值 或者 返回对象值切片 时，并不涉及指向对象的指针，因此该操作不会导致逃逸。
	*/
}
